"""
Licensed Materials - Property of IBM
(c) Copyright IBM Corporation 2010. All Rights Reserved.

Note to U.S. Government Users Restricted Rights:  Use,
duplication or disclosure restricted by GSA ADP Schedule
Contract with IBM Corp.

This code is released under the terms of the Eclipse Public License
version 1.0 which can be found at the top level of this project or
or at http://www.eclipse.org/org/documents/epl-v10.php
"""

INTRO_BLOCK = """
/* 
 * Licensed Materials - Property of IBM
 * (c) Copyright IBM Corporation 2010. All Rights Reserved.
 *
 * Note to U.S. Government Users Restricted Rights:  Use,
 * duplication or disclosure restricted by GSA ADP Schedule
 * Contract with IBM Corp.
 *
 * This code is released under the terms of the Eclipse Public License
 * version 1.0 which can be found at the top level of this project or
 * or at http://www.eclipse.org/org/documents/epl-v10.php
 */

/* this code is automatically generated.
   DO NOT edit by hand.  Use codegenerator.py to regenerate this file */
"""

import sys
import re
import os

print "Working directory is", os.path.abspath(".")

#simple class for line based parsing
class LineProvider(object):
    def __init__(self, lines):
        object.__init__(self)
        self.lines = lines
        self.i = -1

    def getNextLine(self):
        self.i += 1
        # don't return the comment portion of a line
        return self.lines[self.i].split("#")[0]

    def getCurrentLine(self):
        if self.i < 0:
            self.i = 0
        # don't return the comment portion of a line
        return self.lines[self.i].split("#")[0]

    def pushLine(self):
        # so that next time, we return the same one we returned last
        self.i -= 1
    
    # how are there lines left to parse?
    def linesLeft(self):
        return self.i + 1 < len(self.lines)

    # jump ahead to the next line that matches the regular expression
    def skipToMatching(self, pat, includeCurrent = True):
        if includeCurrent and re.match(pat, self.getCurrentLine()):
            return self.getCurrentLine()
        if not self.linesLeft():
            return None
        while not re.match(pat, self.getNextLine()):
            if not self.linesLeft():
                return None
        return self.getCurrentLine()

    # what line are we on?
    def getLineNum(self):
        return self.i+1

# I hate python's string interpolation,
# this allows us to do ruby style stuff!
def interp(string, frame_depth=1):
    # grab the environment of where this was called from...
#n  the expressions in that environment

    locals  = sys._getframe(frame_depth).f_locals
    globals = sys._getframe(frame_depth).f_globals
    for item in re.findall(r'#\{([^}]*)\}', string):
        string = string.replace('#{%s}' % item, str(eval(item, globals, locals)))
    return string

# makes any writer use string interpolation
class InterpolationWriter(object):
    def __init__(self, writer):
        self.writer = writer
        object.__init__(self)

    def write(self, string, frame_depth=1):
        self.writer.write(interp(string, frame_depth+1))
    
    def writeln(self, string, frame_depth=1):
        self.writer.write(interp(string, frame_depth+1) + "\n")

    def close(self):
        self.writer.close()

# code generators should have this interface...
class CodeGenerator:
    def __init__(self, modelClass):
        pass

    def writeModelClassCode(self, out):
        pass

# generate the java model code so we can do useful things later on
# in java-land
class ModelCodeGenerator:
    def __init__(self):
        pass

    def getFilename(self):
        return "JazzMiningModel.java"

    def writeCode(self, out):
        out.writeln(INTRO_BLOCK)
        out.writeln("""
        package com.ibm.mining.model;
        import com.ibm.mining.model.*;

        public class JazzMiningModel extends Model {

        public void buildModel() {
            Property property;""")

        types = {}

        for mc in ModelClass.getConcreteClasses():
            #types.add(mc.className)
            for property in mc.getProperties():
                typ = property.type
                while typ.isListType():
                    types[typ.getModelName()] = typ
                    typ = typ.getListItemType()
                if typ.hasLength() or not typ.isPrimitiveType():
                    types[typ.getModelName()] = typ

        # reorder the types so that list types 
        # are at the end. 
        typeList = []
        for name, typ in types.items():
            if name.startswith("List"):
                typeList.append(typ)
            else:
                typeList.insert(0, typ)


        for typ in typeList:
            if typ.hasLength():
                out.writeln("""\t\tbuildType("#{typ.getName()}", #{typ.getLength()});""")
            else: 
                out.writeln("""\t\tbuildType("#{typ.getModelName()}");""")

        for mc in ModelClass.getConcreteClasses():
            out.writeln("""\t\tModelClass #{mc.className} = buildModelClass("#{mc.className}");""")
            if mc.isHistoryTracked():
                out.writeln("\t\t#{mc.className}.setHistoryTracked(true);")
            for property in mc.getProperties():
                if property.hasTrueAttribute("skip"):
                    continue
                out.writeln('\t\tproperty = #{mc.className}.createProperty("#{property.name}");')
                if property.type.hasLength():
                    out.writeln('\t\tproperty.setType(getType("#{property.type.getModelName()}", #{property.type.getLength()}));')
                else:
                    out.writeln('\t\tproperty.setType(getType("#{property.type.getModelName()}"));')
                desc = re.sub("\s+", " ", property.desc).replace('"', '\\"')
                out.writeln('\t\tproperty.setDescription("#{desc}");')
                if property.hasTrueAttribute("key"):
                    out.writeln("\t\tproperty.setKey(true);")
                if property.hasTrueAttribute("visit"):
                    out.writeln("\t\tproperty.setVisited(true);")
                if property.hasTrueAttribute("track"):
                    out.writeln("\t\tproperty.setTracked(true);")
                

        out.writeln("\t}\n}")
       
#take a plural noun and make it singular
def singular(s):
    if s.endswith("ren"):
        return s[:-3]
    elif s.endswith("ies"):
        return s.replace("ies", "y")
    elif s.endswith("s"):
        return s[:-1]
    else:
        raise Exception("don't know how to make %s singular" % s)

# generate the sql create table commands.  Currently
# works for postgres, should move this to java land later
class SqlSchemaGenerator:
    def __init__(self, modelClass):
        self.modelClass = modelClass

    def writeModelClassCode(self, out):
        mc = self.modelClass
        out.writeln("drop table #{mc.className.lower()};")
        out.writeln("create table #{mc.className.lower()} (")

        columns = []
        toManyProperties = []
        for property in mc.getProperties():
            if property.type.isPrimitiveType():
                columns.append("\t" + property.name + " " + property.type.getDbType())
            elif property.type.isListType():
                toManyProperties.append(property)
            else:
                otherMc = property.type.getClassForType()
                for key in otherMc.getKeys():
                    columns.append("\t" + property.name + "_" + key + " " + otherMc.getProperty(key).type.getDbType())
        columns.append("\tprimary key(" + ", ".join(mc.getKeys()) + ")")
        out.writeln(",\n".join(columns))
        out.writeln(");\n")

        # for one-to-many relationships or many-to-many relationships,
        # where a property of this class is actually a list of things,
        # create a table class_to_property that can map one to many or
        # many to many
        #
        # For an example, see component and baseline
        for property in toManyProperties:
            tableName = mc.className.lower() + "_to_" + property.name
            #print "doing", tableName
            out.writeln("drop table #{tableName};")
            listItemType = property.type.getListItemType()
            out.writeln("create table #{tableName} (")
            cols = []
            for key in mc.getKeys():
                keyProp = mc.getProperty(key)
                colName = mc.className + "_" + keyProp.name

                cols.append("\t" + colName + " " + keyProp.type.getDbType())
            if listItemType.isPrimitiveType():
                #should we try to make the name singular?
                cols.append("\t" + singular(property.name) + " " + listItemType.getDbType())
            else:
                otherMc = listItemType.getClassForType()
                for key in otherMc.getKeys():
                    keyProp = otherMc.getProperty(key)
                    colName = singular(property.name) + "_" + otherMc.className + "_" + keyProp.name
                    cols.append("\t" + colName + " " + keyProp.type.getDbType())

            out.writeln(", \n".join(cols) + "\n);\n")
            
class ItemVisitorGenerator:
    def __init__(self):
        pass
    
    def getFilename(self):
        return "JazzItemVisitor.java"
    
    def generateCode(self, out):
        out.writeln(INTRO_BLOCK)
        out.writeln("package com.ibm.mining.sqldata;\n")
        out.writeln("import java.sql.*;")
        importSet = set()
        for modelClass in ModelClass.getClasses():
            for imp in modelClass.imports:
                importSet.add(imp)
        for imp in importSet:
            out.writeln("import #{imp};")

        out.writeln("public interface JazzItemVisitor {")
        for modelClass in ModelClass.getConcreteClasses():
            out.writeln("\tpublic boolean visit#{modelClass.className}(#{modelClass.fullClassName} instance) throws TeamRepositoryException;")
        out.writeln("}")
            
# this generates a class for each entity type
# for each property of that entity, there is a method
# to retrieve it.
class DataMinerGenerator:
    def __init__(self, modelClass):
        self.modelClass = modelClass

    def getFilename(self):
        return self.modelClass.className + "DataMiner.java"
    
    def generateGetter(self, out, property):
        mc = self.modelClass
        code = property.code
        out.writeln("""static #{property.type.getName()} get#{property.name}(#{self.modelClass.fullClassName} instanceHandle, MinerServices minerServices) 
            throws TeamRepositoryException
            {
                if (instanceHandle == null) return null;""")
        if code.needsInstance():
            # if the code needs an instance, and we have to fetch it from the repository,
            # then 
            if self.modelClass.handle:
                out.writeln("""
                    /* this getter needs an instance, but the repository may not be able to find it if the
                       import was bad, so return null rather then fail if the repo can't find it */
                    #{mc.iClassName} instance = (#{mc.iClassName}) minerServices.getFullState(instanceHandle);
                    if (instance == null) {
                        return null;
                    }""")
                
            else:
                out.writeln("\t\t#{mc.iClassName} instance = instanceHandle;")
        if code.needsMonitor():
            out.writeln("\t\tIProgressMonitor monitor = minerServices.getMonitor();")
        if code.needsWorkspaceManager():
            out.writeln("\t\tIWorkspaceManager workspaceManager = minerServices.getWorkspaceManager();")
        if code.needsRepository():
           out.writeln("\t\tITeamRepository repository = minerServices.getRepository();")
        out.writeln("\t\t#{property.type.getName()} value = #{property.type.getDefault()};")
        if property.hasAttribute("internal_name"):
            out.writeln("""
                if (instance.isPropertySet(#{mc.iClassName}.#{property.internal_name.upper()}_PROPERTY)) {
                    #{code.getReplacedCode()}
                }""")
        else:
            out.writeln("\t\t" + code.getReplacedCode())
            
        out.writeln("\t\treturn value;\n\t}\n")
        

    # generate a unique key for this instance based on properties markes with key: True
    def generateGetKey(self, out):
        out.writeln("""\tstatic String getKey(#{self.modelClass.fullClassName} instanceHandle, MinerServices minerServices) throws TeamRepositoryException
            {
                if (instanceHandle == null) return null;
        """)

        keys = self.modelClass.getKeys();
        out.writeln("""\t\tString key = "";
                Object keyItem = get#{keys[0]}(instanceHandle, minerServices);
                if (keyItem == null) {
                    return null;
                }
                key += keyItem.toString();""")
                
        for key in keys[1:]:
            out.writeln("""
                keyItem = get#{key}(instanceHandle, minerServices);
                if (keyItem == null) {
                    return null;
                }
                key += ":" + keyItem.toString();""")
        out.writeln("\t\treturn key;\n\t}")

    # write the whole class out
    def writeModelClassCode(self, out):
        mc = self.modelClass
        out.writeln(INTRO_BLOCK)
        out.writeln("package com.ibm.mining.sqldata;\n")
        out.writeln("import java.sql.*;")
        for imp in mc.imports:
            out.writeln("import #{imp};")

        out.writeln("public class #{mc.className}DataMiner {")

        for property in mc.getProperties():
            self.generateGetter(out, property)

        self.generateGetKey(out)
        out.writeln("}")



# this generates one class
# that traverses all of the entities, gets their
# properties, and inserts them into the database 
class SqlDataGenerator:
    def __init__(self, modelClasses):
        self.modelClasses = modelClasses

    def getFilename(self):
        return "JazzDataInserter.java"

    def writeGeneratedCode(self, out):
        out.writeln(INTRO_BLOCK)
        out.writeln("package com.ibm.mining.sqldata;\n")
        out.writeln("import java.sql.*;")
        importSet = set()
        for modelClass in self.modelClasses:
            for imp in modelClass.imports:
                importSet.add(imp)
        for imp in importSet:
            out.writeln("import #{imp};")

        out.writeln("""public class JazzDataInserter implements JazzItemVisitor {
            IProgressMonitor monitor;
            ITeamRepository repository;
            IWorkspaceManager workspaceManager;
            Connection conn;
            MinerServices minerServices;
            
            public JazzDataInserter(MinerServices minerServices, Connection conn) {
                this.minerServices = minerServices;
                this.monitor = minerServices.getMonitor();
                this.workspaceManager = minerServices.getWorkspaceManager();
                this.repository = minerServices.getRepository();
                this.conn = conn;
            }""")
        
        self.writeVisitedCode(out)

        for modelClass in self.modelClasses:
            #write out the prepared_statement variables

            out.writeln("\t\tPreparedStatement #{modelClass.className}Stmt;")
            for property in modelClass.getProperties():
                if property.type.isListType():
                    out.writeln("\t\tPreparedStatement #{modelClass.className}To#{property.name}Stmt;")

        out.writeln("\tpublic void createPreparedStatements() throws SQLException {")
        # build all of the prepared statements ahead of time since we'll 
        # be using them a lot
        for modelClass in self.modelClasses:
            self.buildPreparedStatements(out, modelClass)
        out.writeln("\t}")
       
        out.writeln("""\tpublic void visitLinksForItem(IItemHandle itemHandle)
                            throws TeamRepositoryException {
                    /* build the reference to the item and grab links with this item as the source,
                     * grabbing links with this item as the target takes the server WAY longer
                     */
                    IReference itemRef = minerServices.getLinkManager().referenceFactory().createReferenceToItem(itemHandle);
                    ILinkQueryPage page = minerServices.getLinkManager().findLinksBySource(itemRef, minerServices.getMonitor());
            
                    /* traverse the set of links and visit them.  Grab them all at once since there won't
                     * be too many */
                    for (ILink link : page.getAllLinksFromHereOn()) {
                        visitLink(link);
                    }
                    
                    if (itemHandle instanceof IChangeSetHandle) {
                        visitChangeSetLinks( (IChangeSetHandle) itemHandle);
                    }
                }
                
                public void visitChangeSetLinks(IChangeSetHandle changeSetHandle) throws TeamRepositoryException {
                    Logger.getLogger("com.ibm.mining").info("getting change set links");
                    ClientProviderFactory clientProviderFactory = new ClientProviderFactory(
                            minerServices.getRepository());
                    List<ILink> links = ChangeSetLinks.findLinks(clientProviderFactory, 
                            changeSetHandle, minerServices.getMonitor());
                    for (ILink link : links) {
                        visitLink(link);
                    }
                }""") 
        
        #write out the visitor code for each entity 
        for modelClass in self.modelClasses:
            self.writeModelClassCode(out, modelClass)
        out.writeln("}")
        
    # build the prepared statements for the model class
    def buildPreparedStatements(self, out, modelClass):
        toManyProperties = []
        valueNames = []
        for property in modelClass.getProperties():
            if property.type.isListType():
                toManyProperties.append(property)
            elif property.type.isPrimitiveType():
                valueNames.append(property.name)
            else:
                for key in property.type.getClassForType().getKeys():
                    valueNames.append(property.name + "_" + key)

        valueNames.sort()
        out.writeln("""\t\t#{modelClass.className}Stmt = conn.prepareStatement( "INSERT INTO #{modelClass.className} " +
                "(#{", ".join(valueNames)}) "+ 
                "VALUES (#{", ".join(["?"] * len(valueNames))})");""")
        
        #now tackle one-to-many relations for Lists
        for property in toManyProperties:
            #there are two possibilities.  If this is a primitive type, then we just put the value 
            #right into this relation.  Otherwise, we include the key to the non-primitive type
            tableName = modelClass.className + "_to_" + property.name
            valueNames = [modelClass.className + "_" + key for key in modelClass.getKeys()]
            
            listItemType = property.type.getListItemType()
            if listItemType.isPrimitiveType():
                valueNames.append(singular(property.name))
            else:
                otherMc = listItemType.getClassForType()
                for key in otherMc.getKeys():
                    keyProp = otherMc.getProperty(key)
                    colName = singular(property.name) + "_" + otherMc.className + "_" + keyProp.name
                    valueNames.append(colName)

            out.writeln("""\t\t#{modelClass.className}To#{property.name}Stmt = conn.prepareStatement("INSERT INTO #{tableName} " + 
                "(#{", ".join(valueNames)})" + 
                "VALUES (#{", ".join(["?"] * len(valueNames))})");""")
            
    def writeVisitedCode(self, out):
        # first define all of the visited sets
        for modelClass in self.modelClasses:
            out.writeln("""\tSet<String> visited#{modelClass.className} = new HashSet<String>();""")
            
        out.writeln("""\tpublic void fillVisitedSets() throws SQLException {
            String sql;
            Statement stmt = conn.createStatement();
            ResultSet resultSet;
            int numColumns, rowIndex;
            String key;
            """)
        
        for modelClass in self.modelClasses:
            keyNames = modelClass.getKeys()
                
            out.writeln("""\t\tsql = "select #{", ".join(keyNames)} from #{modelClass.className}";
                minerServices.getLogger().info("getting visited keys from database for model class #{modelClass.className}");
                resultSet = stmt.executeQuery(sql);
                while (resultSet.next()) {
                    /* anything that is part of the key should never be null (crosses fingers),
                     * so this should be safe to do */
                    key = resultSet.getObject(1).toString(); """)
            for colIndex in range(2, len(keyNames)+1):
                    out.writeln("""\t\t\tkey += ":" + resultSet.getObject(#{colIndex});""")
            out.writeln("\t\t\tvisited#{modelClass.className}.add(key);")
            out.writeln("\t\t}")
        
        out.write("\t}")
        
        # output a method that tells us if a particular instance has been visited
        for modelClass in self.modelClasses:
            out.writeln("""\tpublic boolean hasVisited#{modelClass.className}(#{modelClass.fullClassName} instanceHandle) throws TeamRepositoryException {
                return visited#{modelClass.className}.contains(#{modelClass.className}DataMiner.getKey(instanceHandle, minerServices));
            }""")
            
            
    # output the visit code for the entity
    def writeModelClassCode(self, out, modelClass):
        mc = modelClass

        properties = mc.getProperties()

        tmp = [property.name for property in properties]
        tmp.sort()
   
        props = []
        for propName in tmp:
            property = mc.getProperty(propName)
            if property.hasAttribute("code"):
                props.append(property)

        # we should actually probably first ping the database to see what is in there.  This
        # only works if the db starts empty, which is bad in general.
        out.writeln("""\tpublic boolean visit#{mc.className}(#{mc.fullClassName} instanceHandle) throws TeamRepositoryException {
                /* if we somehow get a handle that is null, which can happen when the repository
                    can't find the item in a database, then just return for now */
                if (instanceHandle == null) {
                    return false;
                }
                String visitedKey = #{mc.className}DataMiner.getKey(instanceHandle, minerServices);
                
                /* if we somehow get a handle that is null, which can happen when the repository
                    can't find the item in a database, then just return for now */
                if (visitedKey == null) {
                    return false;
                }
                /* if this item has been visited then return */
                if (visited#{mc.className}.contains(visitedKey)) {
                    return false;
                } 
               
                /* look to see if the database even *has* this item in it and
                 * bail otherwise
                 */
                if (instanceHandle instanceof IItemHandle && 
                        minerServices.getFullState((IItemHandle)instanceHandle) == null) {
                        Logger.getLogger("com.ibm.mining").severe(
                            "Can't get full state for #{mc.fullClassName} : " + instanceHandle + " skipping...");
                        return false;
                }
                minerServices.getLogger().info("Visiting #{mc.className} : " + visitedKey);""")

        
        toManyProperties = []
        valueNames = []
        for property in props:
            out.writeln("\t\t#{property.type.getName()} #{property.name} = #{mc.className}DataMiner.get#{property.name}(instanceHandle, minerServices);")
            if property.type.isListType():
                toManyProperties.append(property)
            elif property.type.isPrimitiveType():
                valueNames.append(property.name)
            else:
                for key in property.type.getClassForType().getKeys():
                    valueNames.append(property.name + "_" + key)

        out.writeln("try {")
        argIndex = 1 
        stmtName = mc.className + "Stmt"
        for property in props:
            typ = property.type
            if typ.isListType():
                pass
            elif typ.isPrimitiveType():
                out.writeln("\t\t#{stmtName}.#{typ.getDbSetMethod()}(#{argIndex}, #{property.name});")
                argIndex += 1
            else:
                otherMc = property.type.getClassForType()
                for key in otherMc.getKeys():
                    keyProp = otherMc.getProperty(key)
                    #there should already be a variable with the name of the key
                    out.writeln("\t\t#{stmtName}.#{keyProp.type.getDbSetMethod()}(#{argIndex}, #{otherMc.className}DataMiner.get#{key}(#{property.name}, minerServices));")
                    argIndex += 1

        out.writeln("""\t\t#{stmtName}.execute();""") 
        out.writeln("""} catch (SQLException e) {
                        throw new RuntimeException(e);
                    }""")

        #now tackle one-to-many relations for Lists
        for property in toManyProperties:
            #there are two possibilities.  If this is a primitive type, then we just put the value 
            #right into this relation.  Otherwise, we include the key to the non-primitive type
            stmtName = mc.className + "To" + property.name + "Stmt"
            tableName = mc.className + "_to_" + property.name
            valueNames = [mc.className + "_" + key for key in mc.getKeys()]
            
            listItemType = property.type.getListItemType()
            if listItemType.isPrimitiveType():
                valueNames.append(property.name)
            else:
                otherMc = listItemType.getClassForType()
                for key in otherMc.getKeys():
                    keyProp = otherMc.getProperty(key)
                    colName = otherMc.className + "_" + keyProp.name
                    valueNames.append(colName)
            out.writeln("try {")
            # now create the bind statements
            argIndex = 1
            for key in mc.getKeys():
                keyProp = mc.getProperty(key)
                #there should already be a variable with the name of the key
                out.writeln("\t\t#{stmtName}.#{keyProp.type.getDbSetMethod()}(#{argIndex}, #{key});")
                argIndex += 1
                

            #now loop through all the elements in the list
            otherInstanceHandleType = listItemType.getName()
            
            out.writeln("""
                if (#{property.name} != null) {
                    for (#{listItemType.getName()} #{property.name}_instance : #{property.name}) 
                    {""")
            if listItemType.isPrimitiveType():
                out.writeln("\t\t\t#{stmtName}.#{listItemType.getDbSetMethod()}(#{argIndex}, #{property.name}_instance);")
            else:
                otherMc = listItemType.getClassForType()
                otherClassName = otherMc.className
                for key in otherMc.getKeys():
                    keyProp = otherMc.getProperty(key)
                    keyPropName = keyProp.name
                    colName = otherMc.className + "_" + keyProp.name
                    out.writeln("""\t\t\t#{stmtName}.#{keyProp.type.getDbSetMethod()}(#{argIndex}, 
                            #{otherMc.className}DataMiner.get#{keyProp.name}(#{property.name}_instance, minerServices));""")
                    argIndex += 1

            out.writeln("""\t\t\t#{stmtName}.execute();""")
            out.writeln("""
                        } // end of for loop
                    } // end of if check for null""")
            out.writeln("""\t\t} catch (SQLException e) {
                        throw new RuntimeException(e);
                    \t\t}""")
                
        # if this is an item that we want to visit links for,
        # then visit them now
        if mc.getVisitLinks():
            out.writeln("\t\tvisitLinksForItem(instanceHandle);")
                
        # now generate code to visit the children by looking 
        # for explicit "visitChildren" entries for the class
        # and finding all properties that have visit set to True
        if mc.hasVisitChildrenCode():
            if mc.visitChildrenCode.needsInstance():
                if mc.handle:
                    out.writeln("\t\t#{mc.iClassName} instance = (#{mc.iClassName}) minerServices.getFullState(instanceHandle);")
                else:
                    out.writeln("\t\t#{mc.iClassName} instance = instanceHandle;")
            out.write("\t\t" + mc.getVisitChildrenCode().getReplacedCode())
        
        # we assume that the code
        for property in mc.getProperties():
            typ = property.type
            if property.hasTrueAttribute("visit"):
                if typ.isListType():
                    out.writeln("""
                    if (#{property.name} != null) {
                        for (#{typ.getListItemType().getName()} #{property.name}_instance : #{property.name})
                        {
                            visit#{typ.getListItemType().getClassForType().className}(#{property.name}_instance);
                        }
                    }
                    """)
                else:
                    out.writeln("\t\tvisit#{property.type.getClassForType().className}(#{property.name});")
                   
        # we're done so we should indicate that the item has been visited 
        out.writeln("""
                visited#{mc.className}.add(visitedKey);
                return true;
            }""")




#take a variable_name to VariableName

class HistoryInserterGenerator:
    def getFilename(self): 
        return "JazzItemHistoryInserter.java"
    
    def writeGeneratedCode(self, out):
        out.writeln(INTRO_BLOCK)
        out.writeln("package com.ibm.mining.sqldata;\n")
        out.writeln("import java.sql.*;")
        importSet = set()
        for modelClass in ModelClass.getConcreteClasses():
            for imp in modelClass.imports:
                importSet.add(imp)
        for imp in importSet:
            out.writeln("import #{imp};")
            
        out.writeln("""public class JazzItemHistoryInserter extends AbstractJazzItemHistoryInserter {
            public void getChange(JazzItemChange jic, IAuditable item, IAuditable newerItem) 
                throws TeamRepositoryException{
            """)
        
        for modelClass in ModelClass.getConcreteClasses():
            if modelClass.isHistoryTracked():
                jazzItem = modelClass.iClassName
                out.writeln("""
                if (item instanceof #{jazzItem}) {
                    get#{modelClass.className}Change(jic, (#{jazzItem}) item, (#{jazzItem}) newerItem);
                }
            }
        """)

        for modelClass in ModelClass.getConcreteClasses():
            if modelClass.isHistoryTracked():
                self.writeClassGetChange(out, modelClass)
                
        out.writeln("}") 
        
    def writeClassGetChange(self, out, modelClass):
        out.writeln("""public void get#{modelClass.className}Change(JazzItemChange jic,
            #{modelClass.iClassName} item, #{modelClass.iClassName} newerItem) 
            throws TeamRepositoryException {
                jic.setItemType("#{modelClass.className}");""")
        
       
        for key in modelClass.getKeys():
            out.writeln("""jic.addItemKey("#{modelClass.className}_#{key}", 
                #{modelClass.className}DataMiner.get#{key}(item, minerServices));""")
        
        for property in modelClass.getProperties():
            if property.hasTrueAttribute("track"):
                type = property.type
                if type.isPrimitiveType():
                    out.writeln("""addToChangeIfDifferentPrimitive(jic, "#{property.name}",
                        #{modelClass.className}DataMiner.get#{property.name}(item, minerServices),
                        #{modelClass.className}DataMiner.get#{property.name}(newerItem, minerServices)
                        ); """)
                elif type.isListType():
                    pass
                else: #type is a model class
                    out.writeln("""addToChangeIfDifferentModelClass(jic, "#{property.name}",
                        #{modelClass.className}DataMiner.get#{property.name}(item, minerServices),
                        #{modelClass.className}DataMiner.get#{property.name}(newerItem, minerServices)
                        );""")
        out.writeln("}")
        
        

def CamelCase(s, first=True):
    s2 = ""
    upperNext = first
    for ch in s:
        if upperNext:
            s2 += ch.upper()
            upperNext = False
        elif ch == "_":
            upperNext = True
        else:
            s2 += ch
    return s2

class TemplateCode:
    def __init__(self, code, modelClass):
        self.code = code
        self.modelClass = modelClass

    def resolve(self):
        pass

    def needsMonitor(self):
        self.resolve()
        return "$monitor" in self.code

    def needsRepository(self):
        self.resolve()
        return "$repository" in self.code

    def needsWorkspaceManager(self):
        self.resolve()
        return "$workspaceManager" in self.code

    def needsInstance(self):
        self.resolve()
        return re.search(r"\$instance\W", self.code) != None

    def needsInstanceHandle(self):
        self.resolve()
        return "$instanceHandle" in self.code

    def getRawCode(self):
        return self.code

    def getReplacedCode(self, nameDict={}):
        self.resolve()
        code = self.code
        if self.needsMonitor():
            code = code.replace("$monitor", nameDict.get("monitor", "monitor"))
        if self.needsRepository():
            code = code.replace("$repository", nameDict.get("repository", "repository"))
        if self.needsWorkspaceManager():
            code = code.replace("$workspaceManager", nameDict.get("workspaceManager", "workspaceManager"))
        if self.needsInstance():
            code = code.replace("$instance", nameDict.get("instance", "instance"))
        if self.needsInstanceHandle():
            code = code.replace("$instanceHandle", nameDict.get("instanceHandle", "instanceHandle"))
        code = code.replace("$value", nameDict.get("value", "value")).replace("$class", self.modelClass.fullClassName)
        return code.replace("{{", "").replace("}}", "")


class PropertyCode(TemplateCode):
    def __init__(self, code, propertyName):
        self.propertyName = propertyName
        self.code = code.strip()
        self.resolved = False

    def copy(self):
        if self.resolved:
            raise Exception("Copying a PropertyCode that is already resolved!")
        return PropertyCode(self.code, self.propertyName)

    def setModelClass(self, modelClass):
        self.modelClass = modelClass

    # there is some post-processing that should be done on code,
    def resolve(self):
        if self.resolved:
            return
        mc = self.modelClass
        property = self.modelClass.getProperty(self.propertyName)
        if self.code.startswith("AUTO_CHECK"):
            prefix = self.code.split("_")[2]
            # normally just upper case the name of the property
            # but allow the user to override this with jazz_internal_name
            if not property.hasAttribute("jazz_internal_name"):
                property.jazz_internal_name = property.name.upper()
                
            self.code = interp(""" {{
                if ($instance.isPropertySet(#{mc.iClassName}.
                    #{property.jazz_internal_name}_PROPERTY)) {
                    try {
                        $value = (#{property.type.getName()}) 
                            $instance.#{prefix+CamelCase(self.propertyName)}();
                    } catch (Exception e) {
                        minerServices.logException(e);
                    }
                }
                }}""")
        elif property.hasTrueAttribute("check"):
            # normally just upper case the name of the property
            # but allow the user to override this with jazz_internal_name
            if not property.hasAttribute("jazz_internal_name"):
                property.jazz_internal_name = property.name.upper()
            self.code = interp(""" {{
                if ($instance.isPropertySet(#{mc.iClassName}.
                    #{property.jazz_internal_name}_PROPERTY)) {
                    try {
                        #{self.code}
                    } catch (Exception e) {
                        minerServices.logException(e);
                    }
                }
                }}""")
            
            
        elif self.code == "AUTO":   
            self.code = "{{ $value = (" + property.type.getName() + ") $instance." + CamelCase(self.propertyName, first=False) + "();}}"
        elif self.code.startswith("AUTO_"):
            prefix = self.code.split("_", 1)[1]
            self.code = "{{ $value = (" + property.type.getName() + ") $instance." + prefix + CamelCase(self.propertyName) + "();}}"
        self.resolved = True

# this represents a type that a property can have,
# this is a primitive type, a model class type (an entity),
# or recursively a list of a type
class Type:
    def __init__(self, typ, length=None):
        self.typ = typ.strip()
        self.length=length;
        m = re.search(r"\((\d+)\)", typ)
        if m:
            self.length = int(m.group(1))
            self.typ = self.typ.replace(m.group(0), "")
        
    def copy(self):
        return Type(self.typ, self.length)

    def getName(self):
        self.resolve()
        if "(" in self.typ:
            print "type is", self.typ
            1/0
        return self.typ
    
    def hasLength(self):
        return self.length != None
    
    def getLength(self):
        return self.length
    
    def getDefault(self):
        if self.typ == "String":
            return "null"
        if self.typ == "Boolean":
            return "null"
        if self.typ == "Integer":
            return "null"
        if self.typ == "Timestamp":
            return "null"
        return "null"

    def getModelName(self):
        if self.isListType():
            return "List<" + self.getListItemType().getModelName() + ">"
        elif self.isPrimitiveType():
                return self.getName()
        else:
            return self.getClassForType().className

    def setModelClass(self, modelClass):
        self.modelClass = modelClass

    def isPrimitiveType(self):
        self.resolve()
        return self.typ in ["String", "Integer", "Timestamp", "Boolean"]

    def isListType(self):
        self.resolve()
        return self.typ.startswith("List")

    def getListItemType(self):
        self.resolve()
        if not self.isListType():
            raise Exception("type %s has no ListItemType" % self.typ)
        return Type(re.match("List<(.*)>", self.typ).group(1))

    def getClassForType(self):
        self.resolve()
        for mc in ModelClass.classes.values():
            if mc.fullClassName == self.typ:
                return mc
        raise Exception("Could not find class for type: " + self.typ)
    
    #the db type for this type
    def getDbType(self):
        if self.typ == "String":
            dbType = "text"
        elif self.typ == "Integer":
            dbType = "int"
        elif self.typ == "Timestamp":
            dbType = "timestamp"
        elif self.typ == "Boolean":
            dbType = "boolean"
        else:
            dbType = "varchar(100)"
        return dbType
    
    # the jdbc set method for this type
    def getDbSetMethod(self):
        if self.typ == "String":
            dbType = "setString"
        elif self.typ == "Integer":
            dbType = "setInt"
        elif self.typ == "Timestamp":
            dbType = "setTimestamp"
        elif self.typ == "Boolean":
            dbType = "setBoolean"
        else:
            raise Exception("can't get db set methods for non-primitive type " + self.typ)
        return dbType

    # sometimes a type may refer to the model class it is in with $class
    # resolve this
    def resolve(self):
        if "$class" in self.typ:
            self.typ = self.typ.replace("$class", self.modelClass.fullClassName)


class Property:
    def __init__(self, name, modelClass):
        self.modelClass = modelClass
        self.name = name
        self.attrDict = {"name": name}

    def resolve(self):
        if self.hasAttribute("code"):
            self.code.resolve(self.modelClass)
        if self.hasAttribute("type"):
            self.type.resolve(self.modelClass)

    def addAttribute(self, name, attr):
        if name == "code":
            self.attrDict[name] = PropertyCode(attr, self.name)
        elif name == "type":
            self.attrDict[name] = Type(attr)
        else:
            self.attrDict[name] = attr.strip()

    def __getitem__(self, attr):
        return self.attrDict[attr]

    def __getattr__(self, attr):
        return self.attrDict[attr]

    def hasAttribute(self, attr):
        return self.attrDict.has_key(attr)

    def hasTrueAttribute(self, attr):
        return self.attrDict.has_key(attr) and self.attrDict[attr].lower() == "true"

    def getAttributes(self):
        return self.attrDict

    def copy(self):
        p = Property(self.name, self.modelClass)
        p.attrDict = self.attrDict.copy()
        for attrName in ["code", "type"]:
            if self.hasAttribute(attrName):
                p.attrDict[attrName] = self.attrDict[attrName].copy()
        return p

class ModelClass:
    classes = {}

    @classmethod
    def getClass(cls, name):
        return cls.classes[name]

    @classmethod
    def resolveClasses(cls):
        for modelClass in cls.classes.values():
            modelClass.resolve()

    @classmethod
    def getClasses(cls):
        return cls.classes.values()

    @classmethod
    def getConcreteClasses(cls):
        return [clazz for clazz in cls.classes.values() if not clazz.abstract]

    def __init__(self, name, interface, handle):
        self.imports = []
        self.className = name
        self.superClass = ""
        self.properties = []
        self.interface = interface
        self.handle = handle
        self.superClassName = ""
        self.properties = {}
        self.inheritedPropertyClasses = []
        ModelClass.classes[name] = self
        self.fullClassName = self.className
        self.iClassName = self.className
        if self.interface:
            self.fullClassName = "I" + self.fullClassName
            self.iClassName = self.fullClassName
        if self.handle:
            self.fullClassName += "Handle"
        self.visitChildrenCode = None
        self.resolved = False
        self.abstract = False
        self.visitLinks = False
        self.historyTracked = False
        
    def setVisitLinks(self, visitLinks):
        self.visitLinks = visitLinks
        
    def getVisitLinks(self):
        return self.visitLinks
                    
    def setVisitChildrenCode(self, visitChildrenCode):
        self.visitChildrenCode = TemplateCode(visitChildrenCode, self)

    def getVisitChildrenCode(self):
        return self.visitChildrenCode

    def hasVisitChildrenCode(self):
        return self.visitChildrenCode != None
    
    def setHistoryTracked(self, historyTracked):
        self.historyTracked = historyTracked
        
    def isHistoryTracked(self):
        return self.historyTracked

    def getKeys(self):
        keys = []
        for property in self.getProperties():
            if property.hasTrueAttribute("key"):
                keys.append(property.name)
        keys.sort() #this is super critical!  order really matters!
        if len(keys) < 1:
            raise Exception("There are no primary keys set for Model Class " + self.className)
        return keys


    def addInheritedPropertyClass(self, propClassName):
        self.inheritedPropertyClasses.append(propClassName)

    def addImports(self, imports):
        self.imports.extend(imports)

    def addImport(self, imp):
        self.imports.append(imp)

    def setSuperClassName(self, superClassName):
        self.superClass = superClass

    def setAbstract(self, abstract):
        self.abstract = abstract

    def isAbstract(self):
        return self.abstract

    def createProperty(self, name):
        self.properties[name] = Property(name, self)
        return self.properties[name]

    def getProperty(self, name):
        if self.properties.has_key(name):
            return self.properties[name]
        raise Exception("Could not find property %s in %s" % (name, self.className))

    def resolve(self):
        if self.resolved:
            return
        #print "resolving", self.fullClassName
        #print "inherits", self.inheritedPropertyClasses
        for ihp in self.inheritedPropertyClasses:
            ihpc = ModelClass.getClass(ihp)
            ihpc.resolve()
            for pName in ihpc.getPropertyNames():
                #print "Adding property", pName, "to", self.fullClassName
                if not self.hasProperty(pName):
                    self.properties[pName] = ihpc.getPropertyCopy(pName)

        for property in self.properties.values():
            for attName in ["code", "type"]:
                if property.hasAttribute(attName):
                    property[attName].setModelClass(self)
            if property.hasTrueAttribute("skip"):
                del self.properties[property.name]
        #print "Done resolving", self.fullClassName
        self.resolved = True

    def getPropertyCopy(self, name):
        return self.properties[name].copy()

    def hasProperty(self, name):
        return self.properties.has_key(name)

    def getPropertyNames(self):
        self.resolve()
        return self.properties.keys()

    def getProperties(self):
        props = self.properties.values()
        props.sort(key = lambda x: x.name) 
        return props

    def output(self, out, includeAtts = True):
        name = self.className
        if self.interface:
            name = "(I)" + name
        if self.handle:
            name += "(Handle)"
        print name

        for property in self.getProperties():
            if includeAtts:
                print "\tproperty: " + property.name + " " + str(property.getAttributes())
            else:
                print "\tproperty: %s (%s)" % (property.name, property.type.getName())
        if self.visitChildrenCode:
            print "\tvisitChildrenCode"

class Indent:
    def __init__(self):
        self.stack = [0]

    def push(self, obj):
        if type(obj) == str:
            self.stack.append(self.getLevel(obj))
        elif type(obj) == int:
            self.stack.append(obj)
        else:
            raise Exception("can't push object of type %s on indent stack" % str(type(obj)))
        return self.stack[-1]

    def sameLevel(self, line, val=None):
        if val == None:
            val = self.stack[-1]
        if not line.strip():
            return True
        return self.getLevel(line) == val

    def sameOrGreaterLevel(self, line, val=None):
        if val == None:
            val = self.stack[-1]
        if not line.strip():
            return True
        return self.getLevel(line) >= val
    
    def greaterLevel(self, line, val=None):
        if val == None:
            val = self.stack[-1]
        if not line.strip():
            return True
        return self.getLevel(line) > val

    def current(self):
        return self.stack[-1]

    def pop(self):
        self.stack.pop()

    def getLevel(self, line):
        indentLevel = 0
        for ch in line:
            if ch == " ":
                indentLevel += 1
            elif ch == "\t":
                indentLevel += 8
            else:
                return indentLevel
        return indentLevel

# I hate the python re interface (insert ruby love here). I want to be able to 
# try to match in a conditional and then access the match if it
# actually matches... 
class Matcher:
    def match(self, pat, line):
        self.m = re.match(pat, line)
        return self.m

    def search(self, pat, line):
        self.m = re.search(pat, line)
        return self.m

    def last(self):
        return self.m


def main():


    lineProvider = LineProvider(open(sys.argv[1]).readlines())

    lineProvider.skipToMatching(r"\s+imports:")
    imports = []
    while re.match("\s+", lineProvider.getNextLine()):
        line = lineProvider.getCurrentLine().strip()
        if line:
            imports.append(line)
    print "imports are", imports

    indent = Indent()
    matcher = Matcher()

    schemaOut = InterpolationWriter(open("src/codegen/schema.sql", "w"))

    #see how inefficient this is and I don't even care... :)
    while lineProvider.skipToMatching(r"(\(I\))?\w+(\(Handle\))?\s*:"):
        I, lastClass, handle = re.match(r"(\(I\))?(\w+)(\(Handle\))?", lineProvider.getCurrentLine()).groups()
        #print re.match(r"(\(I\))?(\w+)(\(Handle\))?", lineProvider.getCurrentLine()).groups()
        modelClass = ModelClass(lastClass, I != None, handle != None)
        modelClass.addImports(imports)

        print "found class", lastClass

        indent.push(line)
        
        line = lineProvider.getNextLine()

        #print indent.stack
        
        while indent.greaterLevel(line):
            #print line
            #find the list of imports
            if re.search(r"imports\s*:", line):
                #print "found imports"
                indent.push(line)
                line = lineProvider.getNextLine()
                while indent.greaterLevel(line):
                    if line.strip():
                        modelClass.addImport(line.strip())
                    line = lineProvider.getNextLine()
                indent.pop()
                continue

            elif matcher.search(r"abstract\s*:", line):
                if "true" in line.lower():
                    modelClass.setAbstract(True)
                line = lineProvider.getNextLine()
                continue

            elif matcher.search(r"trackHistory\s*:", line):
                if "true" in line.lower():
                    modelClass.setHistoryTracked(True)
                line = lineProvider.getNextLine()
                continue

            elif matcher.search(r"visitLinks\s*:", line):
                if "true" in line.lower():
                    modelClass.setVisitLinks(True)
                line = lineProvider.getNextLine()
                continue



            elif matcher.search(r"visitChildren\s*:", line):
                indent.push(line)
                line = lineProvider.getNextLine()
                visitChildrenCode = ""
                while indent.greaterLevel(line):
                    visitChildrenCode += line
                    line = lineProvider.getNextLine()
                    modelClass.setVisitChildrenCode(visitChildrenCode)
                indent.pop()
                continue

            #find the list of properties
            elif matcher.search(r"properties\s*(\(.*\))?\s*:", line):
                
                if matcher.last().group(1):
                    for className in matcher.last().group(1)[1:-1].split(","):
                        modelClass.addInheritedPropertyClass(className.strip())
                
                #print "found properties"
                indent.push(line)
                line = lineProvider.getNextLine()
                while indent.greaterLevel(line):
                    #find a particular property
                    if indent.greaterLevel(line) and matcher.match(r"\s*(\w+)\s*:", line):
                        propName = matcher.last().group(1)
                        property = modelClass.createProperty(propName)
                        #print "found property", propName
                        indent.push(line)
                        line = lineProvider.getNextLine()
                        #find all of the attributes of the property
                        while indent.greaterLevel(line):
                            if matcher.match(r"\s*(\w+)\s*:(.*)", line):
                                attName = matcher.last().group(1)
                                attText = matcher.last().group(2)
                                #print "found attribute", attName
                                indent.push(line)
                                line = lineProvider.getNextLine()
                                while indent.greaterLevel(line):
                                    attText += line
                                    line = lineProvider.getNextLine()
                                property.addAttribute(attName, attText)
                                indent.pop()
                            else:
                                raise Exception("don't know what to do with line ***" + line[:-1] + "*** at line %i" % lineProvider.getLineNum())
                        indent.pop()
                        lineProvider.pushLine()
                    line = lineProvider.getNextLine()
                indent.pop()
                continue
                    

            line = lineProvider.getNextLine()
        indent.pop()

    ModelClass.resolveClasses()

    for modelClass in ModelClass.getConcreteClasses():
        modelClass.resolve()


        sqlcg = SqlSchemaGenerator(modelClass)
        sqlcg.writeModelClassCode(schemaOut)
        
        datamg = DataMinerGenerator(modelClass)
        dir = "src/com/ibm/mining/sqldata/"
        datamg.writeModelClassCode(InterpolationWriter(open(dir + datamg.getFilename(), "w")))

    datacg = SqlDataGenerator(ModelClass.getConcreteClasses())
    dir = "src/com/ibm/mining/sqldata/"
    datacg.writeGeneratedCode(InterpolationWriter(open(dir + datacg.getFilename(), "w")))
    
    historycg = HistoryInserterGenerator()
    historycg.writeGeneratedCode(InterpolationWriter(open(dir + historycg.getFilename(), "w")))

    ivg = ItemVisitorGenerator()
    ivg.generateCode(InterpolationWriter(open(dir + ivg.getFilename(), "w")))

    mcg = ModelCodeGenerator()
    dir = "src/com/ibm/mining/model/"
    mcg.writeCode(InterpolationWriter(open(dir + mcg.getFilename(), "w")))
    
    
    schemaOut.close()

if __name__ == "__main__":
    main()
